/*
 * Copyright (C) 2012 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.base;

import com.google.caliper.BeforeExperiment;
import com.google.caliper.Benchmark;
import com.google.caliper.Param;
import java.util.Arrays;
import java.util.Iterator;

/**
 * Benchmarks {@link Joiner} against some common implementations of delimiter-based string joining.
 *
 * @author Adomas Paltanavicius
 */
public class JoinerBenchmark {

    private static final String DELIMITER_STRING = ",";
    private static final char DELIMITER_CHARACTER = ',';

    private static final Joiner JOINER_ON_STRING = Joiner.on(DELIMITER_STRING);
    private static final Joiner JOINER_ON_CHARACTER = Joiner.on(DELIMITER_CHARACTER);

    @Param({"3", "30", "300"})
    int count;
    @Param({"0", "1", "16", "32", "100"})
    int componentLength;

    private Iterable<String> components;

    @BeforeExperiment
    void setUp() {
        String component = Strings.repeat("a", componentLength);
        String[] raw = new String[count];
        Arrays.fill(raw, component);
        components = Arrays.asList(raw);
    }

    /**
     * {@link Joiner} with a string delimiter.
     */
    @Benchmark
    int joinerWithStringDelimiter(int reps) {
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            dummy ^= JOINER_ON_STRING.join(components).length();
        }
        return dummy;
    }

    /**
     * {@link Joiner} with a character delimiter.
     */
    @Benchmark
    int joinerWithCharacterDelimiter(int reps) {
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            dummy ^= JOINER_ON_CHARACTER.join(components).length();
        }
        return dummy;
    }

    /**
     * Mimics what the {@link Joiner} class does internally when no extra options like ignoring
     * {@code null} values are used.
     */
    @Benchmark
    int joinerInlined(int reps) {
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            StringBuilder sb = new StringBuilder();
            Iterator<String> iterator = components.iterator();
            if (iterator.hasNext()) {
                sb.append(iterator.next().toString());
                while (iterator.hasNext()) {
                    sb.append(DELIMITER_STRING);
                    sb.append(iterator.next());
                }
            }
            dummy ^= sb.toString().length();
        }
        return dummy;
    }

    /**
     * Only appends delimiter if the accumulated string is non-empty. Note: this isn't a candidate
     * implementation for Joiner since it fails on leading empty components.
     */
    @Benchmark
    int stringBuilderIsEmpty(int reps) {
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            StringBuilder sb = new StringBuilder();
            for (String comp : components) {
                if (sb.length() > 0) {
                    sb.append(DELIMITER_STRING);
                }
                sb.append(comp);
            }
            dummy ^= sb.toString().length();
        }
        return dummy;
    }

    /**
     * Similar to the above, but keeps a boolean flag rather than checking for the string
     * accumulated so far being empty. As a result, it does not have the above-mentioned bug.
     */
    @Benchmark
    int booleanIfFirst(int reps) {
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            StringBuilder sb = new StringBuilder();
            boolean append = false;
            for (String comp : components) {
                if (append) {
                    sb.append(DELIMITER_STRING);
                }
                sb.append(comp);
                append = true;
            }
            dummy ^= sb.toString().length();
        }
        return dummy;
    }

    /**
     * Starts with an empty delimiter and changes to the desired value at the end of the iteration.
     */
    @Benchmark
    int assignDelimiter(int reps) {
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            StringBuilder sb = new StringBuilder();
            String delim = "";
            for (String comp : components) {
                sb.append(delim);
                sb.append(comp);
                delim = DELIMITER_STRING;
            }
            dummy ^= sb.toString().length();
        }
        return dummy;
    }

    /**
     * Always append the delimiter after the component, and in the very end shortens the buffer to
     * get rid of the extra trailing delimiter.
     */
    @Benchmark
    int alwaysAppendThenBackUp(int reps) {
        int dummy = 0;
        for (int i = 0; i < reps; i++) {
            StringBuilder sb = new StringBuilder();
            for (String comp : components) {
                sb.append(comp);
                sb.append(DELIMITER_STRING);
            }
            if (sb.length() > 0) {
                sb.setLength(sb.length() - DELIMITER_STRING.length());
            }
            dummy ^= sb.toString().length();
        }
        return dummy;
    }
}
